import os
import subprocess
import threading
import signal
from datetime import datetime
from utils import setup_logger, get_log_dir, get_upload_dir
from flask_socketio import SocketIO
import time


class StreamManager:
    SKIP_KEYWORDS = [
        "frame=",
        "fps=",
        "q=",
        "size=",
        "time=",
        "bitrate=",
        "speed=",
        "nan",
        "fd=",
        "aq=",
        "vq=",
        "sq=",
        "A-V:",
        "major_brand",
        "minor_version",
        "compatible_brands",
        "creation_time",
        "handler_name",
        "vendor_id",
        "encoder",
        "ffmpeg version",
        "built with",
        "configuration:",
        "libavutil",
        "libavcodec",
        "libavformat",
        "libavdevice",
        "libavfilter",
        "libswscale",
        "libswresample",
        "libpostproc",
        "Input #",
        "Metadata:",
        "Duration:",
        "Stream #",
        "Stream mapping:",
        "Output #",
        "Press [q] to stop",
        "ffplay version",
        "[aac @",
        "Last message repeated",
        "channel element",
        "Streamed by ZLMediaKit",
        "title           :",
        "[libx264",
        "a=fmtp",
        "a=rtpmap",
        "m=video",
        "t=",
        "v=",
        "c=IN",
        "s=No",
        "o=",
        "SDP",
        "Side",
        "cpb",
    ]

    def __init__(self, socketio=None):
        self.socketio = socketio
        self.logger = setup_logger()
        self.pushing = False
        self.pulling = False
        self.push_processes = {}
        self.pull_processes = {}
        self.stream_threads = {}
        self.stream_commands = {}
        self.log_dir = get_log_dir()
        self.upload_dir = get_upload_dir()
        self.loop_pull_thread = None
        self.loop_pull_stop = threading.Event()

    def log(self, message):
        """记录消息到日志文件"""
        self.logger.info(message)
        if self.socketio:
            self.socketio.emit("stream_log", {"message": message})

    def _execute_command(self, command, stream_type, stream_index):
        """执行命令并捕获输出"""
        try:
            # 启动进程
            creation_flags = 0
            if os.name == "nt":
                creation_flags = subprocess.CREATE_NEW_PROCESS_GROUP
            else:
                creation_flags = 0

            # 修改 preexec_fn
            preexec_fn = None
            if os.name != "nt" and hasattr(os, "setsid"):
                preexec_fn = os.setsid

            process = subprocess.Popen(
                command,
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                creationflags=creation_flags,
                preexec_fn=preexec_fn,
            )
            # 保存进程信息
            process_dict = (
                self.push_processes if stream_type == "push" else self.pull_processes
            )
            process_dict[stream_index] = process
            self.stream_commands[stream_index] = command

            # 实时读取输出
            while True:
                if not process.stdout:
                    break
                line_bytes = process.stdout.readline()
                if not line_bytes:
                    break

                try:
                    line = line_bytes.decode("utf-8").strip()
                except UnicodeDecodeError:
                    try:
                        line = line_bytes.decode("gbk").strip()
                    except UnicodeDecodeError:
                        line = line_bytes.decode("utf-8", errors="ignore").strip()

                # 过滤FFmpeg metadata和统计信息
                if not any(keyword in line for keyword in self.SKIP_KEYWORDS):
                    self.log(f"sm: [{stream_type.upper()}][{stream_index}] {line}")

            # 记录退出状态
            process.wait()
            if process.returncode == 0:
                pass
                # self.log(
                #     f"sm: {stream_type.capitalize()} stream {stream_index} completed"
                # )

        except Exception as e:
            self.log(f"sm: Error executing {stream_type} command: {str(e)}")
        finally:
            # 清理进程引用
            process_dict.pop(stream_index, None)
            self.stream_commands.pop(stream_index, None)

    def _start_streaming_thread(self, stream_type, commands):
        """启动流处理线程"""
        threads = {}
        for i, command in enumerate(commands):
            stream_index = f"{stream_type}_{i+1}"
            thread = threading.Thread(
                target=self._execute_command,
                args=(command, stream_type, stream_index),
                daemon=True,
            )
            thread.start()
            threads[stream_index] = thread
            # self.log(f"sm: Started {stream_type} stream {i+1}/{len(commands)}")
        return threads

    def is_wvp_mode(self, data):
        """
        判断是否为WVP拉流模式：
        - 推流协议必须是RTP；
        - 推流模式是WVP；
        - 拉流协议不限；
        """
        push_protocol = (data.get("push_protocol") or "").upper()
        rtp_mode = (data.get("rtp_mode") or "").strip().lower()
        return push_protocol == "RTP" and rtp_mode == "wvp"

    def start_streaming(self, stream_type, data, config):
        try:
            server_address = data.get("server_address")
            stream_count = int(data.get("stream_count", 1))
            commands = []

            if stream_type == "push":
                # 推流逻辑：WVP模式不推流，前端禁用按钮，后端无须处理
                protocol = data.get("push_protocol")
                video_path = data.get("video_path")

                if not video_path or not os.path.exists(video_path):
                    self.log("sm: 错误：请先上传视频文件")
                    return False, "请先上传视频文件"

                command_template = config["push"].get(protocol)
                if not command_template:
                    self.log(f"sm: 错误：没有找到{protocol}协议的命令模板")
                    return False, f"没有找到{protocol}协议的命令模板"

                # 生成基础流标识（用视频名）
                base_name = os.path.splitext(os.path.basename(video_path))[0]

                for i in range(stream_count):
                    stream_key = f"{base_name}_{i+1}"
                    command = command_template.format(
                        video_path=video_path,
                        server=server_address,
                        stream_key=stream_key,
                    )
                    if self.socketio:
                        self.socketio.emit(
                            "log_message",
                            {
                                "message": self.log(
                                    f"sm: 推流命令 {i+1}/{stream_count}: {command}"
                                )
                            },
                        )
                    commands.append(command)
                self.pushing = True

            else:  # 拉流处理
                protocol = data.get("pull_protocol")
                pull_mode = data.get("pull_mode", "player")
                pull_conf = config["pull"].get(pull_mode, {})
                protocol_conf = pull_conf.get(protocol)

                if not protocol_conf:
                    self.log(f"sm: 错误：没有找到{pull_mode}模式下{protocol}协议的配置")
                    return False, f"没有找到{pull_mode}模式下{protocol}协议的配置"

                # 获取命令模板
                if isinstance(protocol_conf, str):
                    command_template = protocol_conf
                elif isinstance(protocol_conf, dict):
                    if self.is_wvp_mode(data):
                        command_template = protocol_conf.get("wvp")
                    else:
                        command_template = protocol_conf.get("local")
                else:
                    self.log(f"sm: 错误：无效的{protocol}协议配置类型")
                    return False, f"无效的{protocol}协议配置类型"

                if not command_template:
                    self.log(
                        f"sm: 错误：没有找到{pull_mode}模式下{protocol}协议的命令模板"
                    )
                    return False, f"没有找到{pull_mode}模式下{protocol}协议的命令模板"

                # 拉流命令生成
                stream_count = int(data.get("stream_count", 1))
                server_address = data.get("server_address")

                if self.is_wvp_mode(data):
                    # ✅ WVP拉流，必须填写设备ID和通道ID
                    device_id = data.get("device_id", "")
                    channel_id = data.get("channel_id", "")
                    if not device_id or not channel_id:
                        self.log("sm: 错误：WVP模式需要设备ID和通道ID")
                        return False, "WVP模式需要设备ID和通道ID"

                    for i in range(stream_count):
                        command = command_template.format(
                            server=server_address,
                            device_id=device_id,
                            channel_id=channel_id,
                            stream_key=f"{device_id}_{channel_id}",  # 用于兼容有些模板里有stream_key
                        )
                        if self.socketio:
                            self.socketio.emit(
                                "log_message",
                                {
                                    "message": self.log(
                                        f"sm: 拉流命令 {i+1}/{stream_count}: {command}"
                                    )
                                },
                            )
                        commands.append(command)
                else:
                    # ✅ 非WVP拉流，必须有本地视频路径
                    video_path = data.get("video_path", "")
                    if not video_path:
                        self.log("sm: 错误：非WVP模式需要视频文件路径")
                        return False, "非WVP模式需要视频文件路径"

                    base_name = os.path.splitext(os.path.basename(video_path))[0]

                    for i in range(stream_count):
                        stream_key = f"{base_name}_{i+1}"
                        command = command_template.format(
                            server=server_address,
                            stream_key=stream_key,
                        )
                        if self.socketio:
                            self.socketio.emit(
                                "log_message",
                                {
                                    "message": self.log(
                                        f"sm: 拉流命令 {i+1}/{stream_count}: {command}"
                                    )
                                },
                            )
                        commands.append(command)

                if stream_type == "pull" and data.get("loop_mode"):
                    try:
                        loop_count = int(data.get("loop_count", 2))
                        loop_duration = int(data.get("loop_duration", 5))
                        loop_interval = int(data.get("loop_interval", 2))

                        # 记录循环模式参数
                        self.log(
                            f"sm: 已启用循环模式：循环次数：{loop_count}，拉流时长：{loop_duration}s，间隔：{loop_interval}s"
                        )
                        self.pulling = True
                        if self.socketio:
                            self.socketio.emit(
                                "status_update",
                                {"pushing": self.pushing, "pulling": self.pulling},
                            )

                        self.loop_pull_stop.clear()

                        # 定义停止当前轮次拉流的辅助函数
                        def stop_current_pull(stream_indices):
                            """停止指定索引的拉流"""
                            try:
                                for stream_index in stream_indices:
                                    if stream_index in self.pull_processes:
                                        process = self.pull_processes[stream_index]
                                        try:
                                            if os.name == "nt":
                                                # Windows 专用终止方法
                                                subprocess.run(
                                                    f"taskkill /F /T /PID {process.pid}",
                                                    shell=True,
                                                )
                                            else:
                                                # Unix 系统终止方法
                                                if hasattr(os, "killpg") and hasattr(
                                                    os, "getpgid"
                                                ):
                                                    os.killpg(
                                                        os.getpgid(process.pid),
                                                        signal.SIGKILL,
                                                    )
                                                else:
                                                    process.terminate()
                                            process.wait(timeout=5)
                                        except Exception as e:
                                            self.log(
                                                f"sm: 停止 pull 流 {stream_index} 时出错: {str(e)}"
                                            )
                                        finally:
                                            # 从字典中移除
                                            self.pull_processes.pop(stream_index, None)
                                            self.stream_commands.pop(stream_index, None)
                                self.log(f"sm: 当前轮次拉流已停止")
                            except Exception as e:
                                self.log(f"sm: 停止当前轮次拉流出错: {str(e)}")

                        def loop_pull_task():
                            self.log(f"sm: 开始循环拉流任务，共 {loop_count} 次")
                            interrupted = False
                            for i in range(loop_count):
                                if self.loop_pull_stop.is_set():
                                    self.log("sm: 收到停止信号，终止循环")
                                    interrupted = True
                                    break

                                # 打印当前循环信息
                                self.log(f"sm: 开始第 {i+1}/{loop_count} 次循环")

                                # 生成命令
                                temp_commands = []
                                if self.is_wvp_mode(data):
                                    device_id = data.get("device_id", "")
                                    channel_id = data.get("channel_id", "")
                                    for j in range(stream_count):
                                        cmd = command_template.format(
                                            server=server_address,
                                            device_id=device_id,
                                            channel_id=channel_id,
                                            stream_key=f"{device_id}_{channel_id}",
                                        )
                                        temp_commands.append(cmd)
                                        # 打印拉流命令
                                        self.log(f"sm: 拉流命令: {cmd}")
                                else:
                                    video_path = data.get("video_path", "")
                                    base_name = os.path.splitext(
                                        os.path.basename(video_path)
                                    )[0]
                                    for j in range(stream_count):
                                        stream_key = f"{base_name}_{j+1}"
                                        cmd = command_template.format(
                                            server=server_address,
                                            stream_key=stream_key,
                                        )
                                        temp_commands.append(cmd)
                                        # 打印拉流命令
                                        self.log(f"sm: 拉流命令: {cmd}")

                                # 启动当前轮次拉流
                                current_stream_indices = []
                                for j in range(len(temp_commands)):
                                    stream_index = f"pull_loop_{i}_{j}"
                                    thread = threading.Thread(
                                        target=self._execute_command,
                                        args=(temp_commands[j], "pull", stream_index),
                                        daemon=True,
                                    )
                                    thread.start()
                                    current_stream_indices.append(stream_index)

                                # 等待拉流持续时长
                                time.sleep(loop_duration)

                                # 停止当前轮次的流
                                stop_current_pull(current_stream_indices)

                                # 添加循环结束日志
                                if i < loop_count - 1:
                                    self.log(f"sm: 第 {i+1} 次循环结束，等待 {loop_interval}s 后开始下一次")
                                    time.sleep(loop_interval)
                                else:
                                    self.log(f"sm: 第 {i+1} 次循环结束")

                            # 🔧 判断循环是自然结束还是中断
                            if interrupted:
                                self.log(f"sm: 循环拉流被用户中断")
                            else:
                                self.log(f"sm: 已完成全部 {loop_count} 次循环")

                            self.pulling = False
                            self.log("sm: 循环拉流完成")
                            if self.socketio:
                                self.socketio.emit("status_update", {"pushing": self.pushing, "pulling": self.pulling})

                        self.loop_pull_thread = threading.Thread(
                            target=loop_pull_task, daemon=True
                        )
                        self.loop_pull_thread.start()
                        return True, f"已开始循环拉流，共 {loop_count} 次"

                    except Exception as e:
                        self.log(f"sm: 启动循环拉流失败: {str(e)}")
                        return False, f"循环拉流失败: {str(e)}"

                self.pulling = True

            # 启动流线程
            self.stream_threads[stream_type] = self._start_streaming_thread(
                stream_type, commands
            )

            # 日志
            if stream_type == "push":
                log_msg = f"sm: 已启动 {len(commands)} 路推流，协议：{protocol}"
            else:
                mode = data.get("pull_mode", "player")
                log_msg = (
                    f"sm: 已启动 {len(commands)} 路拉流，协议：{protocol}，模式：{mode}"
                )

            self.log(log_msg)
            if self.socketio:
                self.socketio.emit(
                    "status_update", {"pushing": self.pushing, "pulling": self.pulling}
                )

            return True, f"{len(commands)}路{stream_type}已启动"

        except Exception as e:
            self.log(f"sm: 启动{stream_type}时出错: {str(e)}")
            if stream_type == "push":
                self.pushing = False
            else:
                self.pulling = False
            return False, str(e)

    def stop_streaming(self, stream_type, internal_loop=False):
        """停止推流或拉流"""
        if stream_type == "pull":
            self.loop_pull_stop.set()
        try:
            # 获取对应的进程字典
            processes = (
                self.push_processes if stream_type == "push" else self.pull_processes
            )

            # 终止所有进程
            for stream_index, process in list(processes.items()):
                try:
                    if os.name == "nt":
                        # Windows 专用终止方法
                        subprocess.run(f"taskkill /F /T /PID {process.pid}", shell=True)
                    else:
                        # Unix 系统终止方法
                        if hasattr(os, "killpg") and hasattr(os, "getpgid"):
                            os.killpg(os.getpgid(process.pid), signal.SIGKILL)
                        else:
                            process.terminate()
                    process.wait(timeout=5)
                except Exception as e:
                    self.log(
                        f"sm: 停止 {stream_type} 流 {stream_index} 时出错: {str(e)}"
                    )
                finally:
                    processes.pop(stream_index, None)

            # 更新状态
            if stream_type == "push":
                self.pushing = False
            elif not internal_loop:
                self.pulling = False

            # 清理线程引用
            if stream_type in self.stream_threads:
                if not internal_loop:
                    for thread in self.stream_threads[stream_type].values():
                        if thread.is_alive():
                            thread.join(timeout=1)
                self.stream_threads.pop(stream_type, None)

            if self.socketio:
                self.socketio.emit(
                    "status_update", {"pushing": self.pushing, "pulling": self.pulling}
                )

        except Exception as e:
            self.log(f"sm: 停止 {stream_type} 时出错: {str(e)}")
            if stream_type == "push":
                self.pushing = False
            else:
                self.pulling = False
            if self.socketio:
                self.socketio.emit(
                    "status_update", {"pushing": self.pushing, "pulling": self.pulling}
                )
